package io.github.novaframe.logging;

import io.github.novaframe.config.NovaframeConstants;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;

/**
 * Aspect for logging execution of service and repository Spring components.
 * By default, it only runs with the "dev" profile.
 */

/**
 *  使用@Aspect注解将一个java类定义为切面类
 *  使用@Pointcut定义一个切入点，可以是一个规则表达式，比如下例中某个package下的所有函数，也可以是一个注解等
 * 	使用@Before在切入点开始处切入内容
 * 	使用@After在切入点结尾处切入内容
 * 	使用@AfterReturning在切入点return内容之后切入内容（可以用来对处理返回值做一些加工处理）
 * 	使用@Around在切入点前后切入内容，并自己控制何时执行切入点自身的内容
 * 	使用@AfterThrowing用来处理当切入内容部分抛出异常之后的处理逻辑
 */
@Aspect
public class LoggingAspect {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private final Environment env;
    
    private ThreadLocal<Long> startTime = new ThreadLocal<>();

    public LoggingAspect(Environment env) {
        this.env = env;
    }

    /**
     * web层切点
     */
    @Pointcut("execution(public * com..web.rest..*.*(..))")
    //@Pointcut(value = "${jwt.route.authentication.refresh}")
    public void webLoggingPointcut() {
    	
    }

    /**
     * web层、服务层
     * Pointcut that matches all repositories, services and Web REST endpoints.
     */
    @Pointcut("within(com..web.rest..*) || within(com..service..*)")
    public void loggingPointcut() {
        // Method is empty as this is just a Pointcut, the implementations are in the advices.
    }

    /**
     * 方法异常后
     * Advice that logs methods throwing exceptions.
     *
     * @param joinPoint join point for advice
     * @param e exception
     */
    @AfterThrowing(pointcut = "loggingPointcut()", throwing = "e")
    public void logAfterThrowing(JoinPoint joinPoint, Throwable e) {
        if (env.acceptsProfiles(NovaframeConstants.SPRING_PROFILE_DEVELOPMENT)) {
            logger.error("Exception in {}.{}() with cause = \'{}\' and exception = \'{}\'", joinPoint.getSignature().getDeclaringTypeName(),
                joinPoint.getSignature().getName(), e.getCause() != null? e.getCause() : "NULL", e.getMessage(), e);
        } else {
            logger.error("Exception in {}.{}() with cause = {}", joinPoint.getSignature().getDeclaringTypeName(),
                joinPoint.getSignature().getName(), e.getCause() != null? e.getCause() : "NULL");
        }
    }

    /**
     * 方法进入和退出
     * Advice that logs when a method is entered and exited.
     *
     * @param joinPoint join point for advice
     * @return result
     * @throws Throwable throws IllegalArgumentException
     */
    @Around("loggingPointcut()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        logger.debug("Enter: {}.{}() with argument[s] = {}", joinPoint.getSignature().getDeclaringTypeName(),
            joinPoint.getSignature().getName(), Arrays.toString(joinPoint.getArgs()));
        try {
            Object result = joinPoint.proceed();
            logger.debug("Exit: {}.{}() with result = {}", joinPoint.getSignature().getDeclaringTypeName(),
                joinPoint.getSignature().getName(), result);
            return result;
        } catch (IllegalArgumentException e) {
            logger.error("Illegal argument: {} in {}.{}()", Arrays.toString(joinPoint.getArgs()),
                    joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());

            throw e;
        }
    }

    @Order(1)
    @Before("webLoggingPointcut()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
    	startTime.set(System.currentTimeMillis());
    	
        // 接收到请求，记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 记录下请求内容
        logger.debug("WEB_METHOD URL->{}, HTTP_METHOD->{}, REMOTE_IP->{}." + request.getRequestURL().toString(), request.getMethod(), request.getRemoteAddr());
        //logger.debug("HTTP_METHOD : " + request.getMethod());
        //logger.debug("IP : " + request.getRemoteAddr());
        //logger.debug("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        //logger.debug("ARGS : " + Arrays.toString(joinPoint.getArgs()));
    }
    
    @AfterReturning(returning = "ret", pointcut = "webLoggingPointcut()")
    public void doAfterReturning(Object ret) throws Throwable {
        // 处理完请求，返回内容
        logger.debug("WEB_METHOD RESPONSE : {}", ret);
        logger.debug("WEB_METHOD SPEND TIME : {} ms" ,(System.currentTimeMillis() - startTime.get()));
    }
}
